require 'socket'
require 'iconv'
require 'gtk2'
# WARNING: OUTDATED COMMENTS
# This class will hold a XSIRC instance. It does the basic IRC protocol
# stuff for plug-ins. It basically works like this:
# 1. Initialize, get config options, connect to IRC server
# 2. Run commands specified in the config file, such as joining channels, identifying with services, etc.
# 3. Run the plug-in hooks on PRIVMSGs, KICKs and on every loop iteration
# 4. Repeat 3.
class IRCBot
	# This class handles plug-ins. All plug-ins are instances of this
	# class.
	# = How it works
	# Take the following sample plug-in, named "sample.rb":
	#	IRCBot::Plugin.define "sample" do
	#		help_string "I'm a sample plug-in."
	#		
	#		def command(user,target,message,hostmask)
	#			# This will run on every call to "!sample"
	#		end
	#		
	#		def privmsg_function(user,target,message,hostmask)
	#			# This will run on every message.
	#		end
	#		
	#		def loop_hook()
	#			# This will run on every iteration of the loop.
	#		end
	#	end
	# That code calls IRCBot::Plugin.define, giving the plug-in name and
	# its code as parameters. Help_string is defined via IRCBot::Plugin.def_field,
	# and is available as an instance variable.
	class Plugin
		@registered_plugins = {}
		class << self
			attr_reader :registered_plugins
			private :new
			# This will define a field for plug-in information, doing the
			# same as
			#	@var = "something"
			#	attr_getter :var
			# It's used for help information, and will be used in the future
			# to register dependencies.
			def def_field(*names)
				class_eval do
					names.each do |name|
						define_method(name) do |*args|
							case args.size
								when 0
									instance_variable_get("@#{name}")
								else
									instance_variable_set("@#{name}",*args)
							end
						end
					end
				end
			end
		end
		# This is the most important method. It creates a new plug-in and
		# registers it. It spawns a new IRCBot::Plugin class instance,
		# uses instance_eval to add the code to it, and adds the instance
		# to the <tt>registered_plugins</tt> hash.
		def self.define(name,&block)
			if !$config['global']['plugin_blacklist'].include?(name) then
				plug = new
				plug.instance_eval(&block)
				Plugin.registered_plugins[name] = plug
				puts "Registered new plugin #{name}."
			else
				puts "Ignoring blacklisted plugin #{name}."
			end
		end
		# A wrapper for IRCBot.privmsg using the current instance.
		def privmsg(target,message,prior=1)
			$ircbot.privmsg(target,message,prior)
		end
		# A wrapper for IRCBot.send using the current instance.
		def send(message)
			$ircbot.send(message)
		end
		# Returns the plug-in with that name or nil if it doesn't exist.
		def get_plugin(name)
			Plugin.registered_plugins[name]
		end
		# Registers the :help_string field.
		def_field(:help_string,:depends)
		@depends = []
	end
	
	# Starts up the bot. Loads plug-ins and sets some instance variables.
	def initialize(config)
		if config.class != Hash then
			puts "Not a valid config file, exiting."
			exit(1)
		elsif config['network'].class != Hash then
			puts "Not a valid network, exiting."
			exit(1)
		else
			@config = config
			puts "Loading plugins..."
			Dir["#{@config['global']['plugindir']}/*.rb"].each do |file|
				puts "Loading plugin #{file}..."
				load file
				puts "Plugin #{file} loaded."
			end
			puts "Checking dependencies..."
			missing_deps_plugins = {}
			for plugin in Plugin.registered_plugins
				missing_deps = []
				if plugin[1].depends
					for dependency in plugin[1].depends
						if !Plugin.registered_plugins.include?(dependency) then
							missing_deps << dependency
						end
					end
					if !missing_deps.empty? then
						missing_deps_plugins[plugin[0]] = missing_deps
					end
				end
			end
			if !missing_deps_plugins.empty? then
				puts "Dependency errors! The following plug-ins have unmet dependencies:"
				for plugin in missing_deps_plugins
					puts "#{plugin[0]}:"
					puts plugin[1].join(', ')
				end
				puts "Take note. Making a pause."
				sleep(10)
				puts "I'm assuming that you copy-pasted that. Unloading said plug-ins."
				for plugin in missing_deps_plugins
					Plugin.registered_plugins.delete plugin[0]
				end
				puts "Resuming execution."
			end
			puts "Starting GUI..."
			
		end
		# Globals
		@users = {}
		@rawusers = {}
		@pretty_users = {}
		@nick = $config['network']['nickname']
		@channels = []
		@message_queue = []
		@topics = {}
		if File.exists?('version_number') then
			@version = File.read('version_number').strip
		else
			@version = 'Trunk'
		end
		@lastactive = ''
		@last_recieved = Time.now
		start_gui()
		@destroyed = false
		@socket = false
		@lastsaid = ''
		@connected = false
	end
	
	# Sends a message to the IRC server.
	def send(message)
		@socket.send("#{message}\n",0)
		puts "--> #{message}"
#		handle_server_input(":#{@nick}!baz@augh #{message}") # FIXME: terrible hack
		
		if message.downcase.start_with?("privmsg") then
			message = Iconv.conv('utf-8','ISO-8859-1',message)
			if message.sub(message.split(' :')[0]+' :','').strip.start_with?("#{1.chr}ACTION") then
				message = message.sub("#{1.chr}ACTION",'').chop.strip
				add_to_view(message.split[1].downcase,"#{@nick} #{message.sub(message.split(' :')[0]+' :','').strip}")
			else
				add_to_view(message.split[1].downcase,"<#{@nick}> #{message.sub(message.split(' :')[0]+' :','').strip}")
			end
		end
	end
	
	# Connects to IRC. Opens the socket, and sends USER and NICK.
	def connect()
		@socket = TCPSocket.open(@config['network']['server'],@config['network']['port'])
		send("USER #{@config['network']['username']} foo baz :#{@config['network']['realname']}")
		send("NICK #{@config['network']['nickname']}")
		@connected = true
	end
	
	# Handles server input. Checks its contents and treats them.
	def handle_server_input(s)
		s = s.strip.chomp
		s_spaces = s.sub(":","").split
		s_colons = s.split(":")
		message = s.sub(s.split(' :')[0]+' :','').strip
		if s_spaces[0] == "PING" then
			send "PONG "+s_spaces[1].sub(":","")
		end
		case s_spaces[1]
			when "NOTICE"
				sender = s_spaces[0].sub(':','').split('!')[0]
				target = s_spaces[2]
				message = s.sub(s.split(' :')[0]+' :','').strip
				if target.start_with?("#") then
					add_to_view(target.downcase,"NOTICE from #{sender}: #{message}")
				else
					add_to_view(@channeltabs[@tabholder.page],"NOTICE from #{sender}: #{message}")
				end
			when "PRIVMSG"
				user = s_spaces[0].sub(":","").split("!")[0]
				target = s_spaces[2]
				handle_message(user,target,message,s_spaces[0].sub(":",""))
				if !target.start_with?('#') then
					target = user
				end
				if message.start_with?("#{1.chr}ACTION") then
					message = message.sub("#{1.chr}ACTION",'').chop.strip
					add_to_view(target,"#{user} #{message}")
				else
					add_to_view(target,"<#{user}> #{message}")
				end
			when "INVITE"
				add_to_view(@channeltabs[@tabholder.page],"#{s.sub(':','').split('!')[0]} has invited you to #{s_spaces[3]}.")
			when 'TOPIC'
				@topics[s_spaces[2].downcase] = []
				@topics[s_spaces[2].downcase][1] = s_spaces[0].sub(":","").split("!")[0]
				@topics[s_spaces[2].downcase][2] = Time.new.to_i
				topic_message = s.sub("#{s.split(' :')[0]} :","").strip
				@topics[s_spaces[2].downcase][0] = topic_message
				add_to_view(s_spaces[2].downcase,"The topic for #{s_spaces[2]} is: #{topic_message}")
				add_to_view(s_spaces[2].downcase,"Topic set by #{s_spaces[0].sub(":","").split("!")[0]} right now.")
				if s_spaces[2].downcase == @channeltabs[@tabholder.page] then
					@title_label.text = @topics[s_spaces[2].downcase][0]
				end
			when 'KICK'	
				send("NAMES #{s_spaces[2].sub(":","")}")
				kicker	=	s_spaces[0].sub(':','').split('!')[0].strip
				puts "kicker: '#{kicker}'"
				channel	=	s_spaces[2].strip
				puts "channel: '#{channel}'"
				kicked	=	s_spaces[3].strip
				puts "kicked: '#{kicked}'"
				message	=	s.sub("#{s_spaces[0]} #{s_spaces[1]} #{s_spaces[2]} #{s_spaces[3]} :",'').strip.sub(':','')
				puts "message: '#{message}'"
				add_to_view(channel,"#{kicked} has been kicked from #{channel} by #{kicker} [#{message}]")
				for plugin in Plugin.registered_plugins
					if plugin[1].methods.include?('kick_function') then
						plugin[1].kick_function(kicker,channel,kicked,message)
					end
				end
			when 'MODE'
				if s_spaces[4] != nil then
					add_to_view(s_spaces[2].downcase,"#{s_spaces[0].sub(':','').split('!')[0]} sets mode #{s_spaces[3]} on #{s_spaces[4]}.")
				else
					if s_spaces[2].start_with?('#') then
						add_to_view(s_spaces[2].downcase,"#{s.sub(':','').split('!')[0]} set #{s_spaces[2]}'s mode: #{s_spaces[3]}")
					else
						add_to_view('network',"MODE #{@nick} #{s_spaces[3]}")
					end
				end
				for channel in @channels
					send("NAMES #{channel}")
				end
			when 'PART'
				if s_spaces[0].split("!")[0] == @nick then
					@channels.delete(s_spaces[2].downcase)
					@tabholder.remove_page(@tabholder.page_num(@channelscroll[s_spaces[2].downcase]))
				end
				if s == s.sub("#{s.split(' :')[0]} :","").strip then
					add_to_view(s_spaces[2].downcase,"#{s_spaces[0].split("!")[0]} has left #{s_spaces[2]} []")
				else
					add_to_view(s_spaces[2].downcase,"#{s_spaces[0].split("!")[0]} has left #{s_spaces[2]} [#{s.sub("#{s.split(' :')[0]} :","").strip}]")
				end
				send("NAMES #{s_spaces[2].sub(":","")}")
			when 'JOIN'
				if s_spaces[0].split("!")[0] == @nick then
					@channels.push(s_spaces[2].sub(":","").downcase)
					open_view(s_spaces[2].sub(":","").downcase) unless @tabholder.page_num(@channelscroll[s_spaces[2].sub(":","").downcase.downcase]) != -1
					add_to_view(s_spaces[2].sub(":","").downcase,"Joining channel #{s_spaces[2].sub(":","").downcase}")
				end
				add_to_view(s_spaces[2].sub(":","").downcase,"#{s_spaces[0].split('!')[0]} has joined #{s_spaces[2].sub(":","")}")
				send("NAMES #{s_spaces[2].sub(":","").downcase}")
			when 'QUIT'
				for channel in @channels
					quitter = s.sub(':','').split('!')[0]
					message = s.sub(s.split(' :')[0]+' :','').strip
					if @users[channel.downcase].include?(quitter.downcase) then
						if s == message then
							add_to_view(channel.downcase,"#{quitter} has left IRC []")
						else
							add_to_view(channel.downcase,"#{quitter} has left IRC [#{message}]")
						end
						send("NAMES #{channel}")
					end
				end
			when 'NICK'
				newnick = s_spaces[2].sub(":","").strip
				if s_spaces[0].sub(':','').split("!")[0].downcase == @nick.downcase then
					@nick = newnick
#					$config['network']['nickname'] = newnick
				end
				for channel in @channels
					if @users[channel.downcase].include?(s_spaces[0].sub(':','').split("!")[0].downcase) then
						add_to_view(channel.downcase,"#{s_spaces[0].sub(':','').split("!")[0]} is now known as #{newnick}.")
						send("NAMES #{channel}")
					end
				end
			
			# Server messages go after this point
			
			when '375'
				startup_routine()
			when '353'
				puts "recieving NAMES list"
				userlist = s_spaces
				userlist = userlist.join(" ")
				userlist = userlist.sub("#{s_spaces[0]} #{s_spaces[1]} #{s_spaces[2]} #{s_spaces[3]} #{s_spaces[4]} ","")
				outgoing_userlist = userlist.sub(':','').split.sort
				rawuserlist = userlist.downcase.sub(":","").split
				userlist.gsub!(/(\+|%|@|&|:)/,"")
				userlist = userlist.downcase.split
				if @users[s_spaces[4].downcase].frozen? or @rawusers[s_spaces[4].downcase] == nil then
					puts "Clearing list"
					@users[s_spaces[4].downcase] = []
					@rawusers[s_spaces[4].downcase] = []
					@pretty_users[s_spaces[4].downcase] = []
				end
				puts "Appending list"
				@users[s_spaces[4].downcase] = @users[s_spaces[4].downcase] + userlist
				@rawusers[s_spaces[4].downcase] = @rawusers[s_spaces[4].downcase] + rawuserlist
				@pretty_users[s_spaces[4].downcase] = (@pretty_users[s_spaces[4].downcase] + outgoing_userlist).sort
				puts userlist.join(", ")
				
#				add_to_view(s_spaces[4].downcase,"In #{s_spaces[4]}: #{outgoing_userlist}")
			when '366'
				puts "Locking #{s_spaces[3].downcase}'s userlist"
				@users[s_spaces[3].downcase].freeze
				if @pretty_users[@channeltabs[@tabholder.page]] then
					@users_label.buffer.text = @pretty_users[@channeltabs[@tabholder.page]].sort.join("\n")
				else
					@users_label.buffer.text = "Not a channel!"
				end
#				add_to_view(s_spaces[3].downcase,"End of /NAMES list")
			when '433'
				send("NICK #{@nick}_")
				@nick = $config['network']['nickname'] = "#{@nick}_"
			when '332'
				@topics[s_spaces[3].downcase] = [] if @topics[s_spaces[3].downcase].class != Array
				@topics[s_spaces[3].downcase][0] = s.sub("#{s.split(' :')[0]} :","").strip
				add_to_view(s_spaces[3].downcase,"The topic for #{s_spaces[3]} is: #{@topics[s_spaces[3].downcase][0]}")
				if s_spaces[3].downcase == @channeltabs[@tabholder.page] then
					@title_label.text = @topics[s_spaces[3].downcase][0]
				end
			when '333'
				@topics[s_spaces[3].downcase] = [] if @topics[s_spaces[3].downcase].class != Array
				@topics[s_spaces[3].downcase][1] = s_spaces[4]
				@topics[s_spaces[3].downcase][2] = s_spaces[5]
				add_to_view(s_spaces[3].downcase,"Topic set by #{s_spaces[4]} #{Time.at(s_spaces[5].to_i).strftime("%a %d %b %Y at %H:%M:%S")}")
		end
		if s_spaces[1].start_with?('4') or s_spaces[1].start_with?('5') then
			add_to_view('network',"#{s_spaces[1]}: #{message}")
		end
		case s_spaces[1]
			when '301'
				add_to_view(s_spaces[3],"User is away")
			when '302'
				add_to_view('network',"USERHOST reply: #{message}")
			when '303'
				add_to_view('network',message)
			when '305'
				add_to_view('network',message)
			when '306'
				add_to_view('network',message)
			# WHOIS handling
			when '311'
				add_to_view('network',"Info for #{s_spaces[3]}!#{s_spaces[4]}@#{s_spaces[5]}: Real name: #{message}")
			when '312'
				add_to_view('network',"Info for #{s_spaces[3]}: in server #{s_spaces[4]}, #{message}")
			when '313'
				add_to_view('network',"Info for #{s_spaces[3]}: #{message}")
			when '317'
				add_to_view('network',"Info for #{s_spaces[3]}: #{s_spaces[4]} seconds idle")
			when '318'
				add_to_view('network',"End of /WHOIS list for #{s_spaces[3]}.")
			when '319'
				add_to_view('network',"Info for #{s_spaces[3]}: In channels #{message}")
			# WHOWAS
			when '314'
				add_to_view('network',"WHOWAS: "+s_spaces[3]+'!'+s_spaces[4]+'@'+s_spaces[5]+": #{message}")
			when '369'
				add_to_view('network',"End of /WHOWAS list.")
			# LIST
			when '321'
				add_to_view('network',"Starting list: Channel, users, topic")
			when '322'
				add_to_view('network',"#{s_spaces[3]}, #{s_spaces[4]}, #{message}")
			when '323'
				add_to_view('network',"End of /LIST list.")
			# Channel mode
			when '324'
				add_to_view(s_spaces[3].downcase,"#{s_spaces[3]}'s modes: #{s_spaces[4]} #{(s_spaces[5] ? s_spaces[5] : '')}")
			when '329'
				add_to_view(s_spaces[3].downcase,"Channel created #{Time.at(s_spaces[4].to_i).strftime("%a %d %b %Y at %H:%M:%S")}")
			when '341'
				add_to_view('network',"Inviting #{s_spaces[3]} to #{s_spaces[4]}")
			when '351'
				add_to_view('network',"Server version #{s_spaces[3]}.#{s_spaces[4]} #{s_spaces[5]}")
			# WHO
			when '352'
				add_to_view('network',"WHO info: in #{s_spaces[3]}, #{s_spaces[7]}!#{s_spaces[4]}@#{s_spaces[5]} via server #{s_spaces[6]}; #{s_spaces[8]} :#{message}")
			when '315'
				add_to_view('network',"End of /WHO list.")
			# LINKS
			when '364'
				add_to_view('network',"Link for #{s_spaces[3]}: #{s_spaces[4]} #{message}")
			when '365'
				add_to_view('network',"End of /LINKS list.")
			# Banlists
			when '367'
				add_to_view('network',"Ban for #{s_spaces[3]}: #{s_spaces[4]}")
			when '368'
				add_to_view('network',"End of ban list for #{s_spaces[3]}.")
			when '371'
				add_to_view('network',"INFO: #{message}")
			when '374'
				add_to_view('network',"End of /INFO list.")
			# MOTD
			when '375'
				add_to_view('network',message)
			when '372'
				add_to_view('network',message)
			when '376'
				add_to_view('network',"End of /MOTD command.")
			when '381'
				add_to_view('network',message)
			when '391'
				add_to_view('network',"Time for #{s_spaces[3]}: #{message}")
			# USERS
			when '392'
				add_to_view('network',message)
			when '393'
				add_to_view('network',message)
			when '394'
				add_to_view('network',message)
			when '395'
				add_to_view('network',message)
			# misc misc
			when '001'
				add_to_view('network',message)
			when '002'
				add_to_view('network',message)
			when '003'
				add_to_view('network',message)
			when '004'
				add_to_view('network',s_spaces[3..s_spaces.count].join(' '))
			when '005'
				add_to_view('network',s_spaces[3..s_spaces.count].join(' '))
			when '251'
				add_to_view('network',message)
			when '252'
				add_to_view('network',s_spaces[3]+' '+message)
			when '254'
				add_to_view('network',s_spaces[3]+' '+message)
			when '255'
				add_to_view('network',message)
			when '265'
				add_to_view('network',message)
			when '266'
				add_to_view('network',message)
		end
	end
	
	# Sends the startup commands as supplied in the config.
	def startup_routine()
		for command in @config['network']['commands']
			send command.gsub("$nick",@nick)
		end
	end
	
	# Treats PRIVMSGs. Checks if they start with the command identifier,
	# checking plug-ins for handlers and calling them, and calls the plug-ins'
	# on_privmsg handlers.
	def handle_message(user,target,message,usermask)
		if !target.start_with?("#") then
			target = user
		end
		for plugin in Plugin.registered_plugins
			if plugin[1].methods.include?('privmsg_function') then
				plugin[1].privmsg_function(user,target,message,usermask)
			end
		end
	end
	# Dummy function, deprecated.
	def log(*args) # Dummy
		
	end
	# Keeps the bot alive. Checks for socket activity, and passes the input
	# to IRCBot.handle_server_input if so, manages the PRIVMSG queue, and
	# calls plug-ins' main_loop hooks.
	def main_loop() #FIXME: high CPU usage?
			while true
				while (Gtk.events_pending?)
					Gtk.main_iteration
				end
				if @destroyed then break; end
				if @socket then
					ready = select([@socket],nil,nil,0.01)
				else
					ready = false
				end
				if ready then
#					return if @socket.eof
					s = @socket.gets
					if s
						puts s
						handle_server_input(s) if s.class == String
					else
						@connected = false
					end
					@last_recieved = Time.now
				end
				if !@connected and @lastsaid != "Not connected." then
					@statusbar.push(@statusbar.get_context_id('connection'),"Not connected.")
					@lastsaid = "Not connected."
				elsif @connected and @lastsaid != "Connected!" then
					@statusbar.push(@statusbar.get_context_id('connection'),"Connected!")
					@lastsaid = "Connected!"
				end
				if Time.now - @last_recieved >= 300 then
					add_to_view('network',"ATTENTION: Ping timeout.")
					add_to_view('network',"Reconnecting...")
					disconnect()
					connect()
					@last_recieved = Time.now
				end
				if !@message_queue.empty? then
					message = @message_queue[0]
					@message_queue.delete message
					case message[2]
						when 2
#							sleep 1.5
						when 1
#							sleep 0.5
					end
					send_message(message[0],message[1])
				end
				for plugin in Plugin.registered_plugins
					if plugin[1].methods.include?('loop_hook') then
						plugin[1].loop_hook
					end
				end
				@channels = @channels.uniq
			end
	end
	# Disconnects the socket
	def disconnect()
		@socket.close
		@socket = false
		@connected = false
	end
	# Adds a message to the outgoing queue, splitting every 380 chars
	# respecting word limits.
	def privmsg(target,message,priority=1)
		if message.class == String then
			if message.length >= 380 then
				output_array = []
				while !message.empty?
					output = ''
					for word in message.strip.split
						break if output.length >= 380
						output << word+' '
					end
					message = message.sub(output.strip,'').strip
					output_array << output.strip
				end
				for item in output_array
					@message_queue.push [target,item,priority]
				end
			else
				@message_queue.push [target,message,priority]
			end
		end
	end
	
	# Actually sends PRIVMSGs.
	def send_message(target,message)
		send("PRIVMSG #{target} :#{message}")
	end
	private :send_message
	attr_reader :config,:users,:channels,:version,:topics
	attr_accessor :socket,:message_queue
	
	# The GUI code goes here.
	def start_gui()
		@channelscroll = Hash.new
		@channellabel = Hash.new
		@channeltabs = []
		@channeltext = {}
		@mainwindow = Gtk::Window.new
		@mainwindow.resizable = true
		@mainwindow.title = "XSIRC version 2"
		@mainwindow.set_size_request(800,600)
		# h and v boxes
		vbox = Gtk::VBox.new(false,1)
		hbox = Gtk::HPaned.new()
		vbox_left = Gtk::VBox.new(false,1)
		vbox_right = Gtk::VBox.new(false,1)
		@statusbar = Gtk::Statusbar.new
		@title_label = Gtk::Label.new("XSirc")
#		eventbox = Gtk::EventBox.new
#		eventbox.events = Gdk::Event::BUTTON_PRESS_MASK
#		eventbox.add(@title_label)
		vbox.pack_start(@title_label,false)
#		eventbox.realize
		vbox.pack_start(hbox)
		vbox.pack_start(@statusbar,false)
		# expander for the connect/disconnect buttons
#		button_expander = Gtk::Expander.new("Connection options")
		hbox.add(vbox_left)
		hbox.add(vbox_right)
		# Buttons!
	#	connect_button = Gtk::Button.new("_Connect")
	#	disconnect_button = Gtk::Button.new("_Disconnect")
		@tabholder = Gtk::Notebook.new
		@tabholder.show_border = true
		@tabholder.show_tabs = "left"
		@tabholder.show_all
		@commandline = Gtk::Entry.new
		@commandline.max_length = 510
		# packing boxen
		# expander for the connect/disconnect buttons
	#	buttonbox = Gtk::VBox.new(false,1)
	#	buttonbox.pack_start(connect_button,false)
	#	buttonbox.pack_start(disconnect_button,false)
	#	button_expander.add(buttonbox)
#		vbox_left.pack_start(button_expander)
		users_label = Gtk::Label.new
		users_label.markup="<span weight=\"bold\">Users:</span>"
		@users_label = Gtk::TextView.new
		@users_label.editable = false
		@users_label.cursor_visible = false
		scroll = Gtk::ScrolledWindow.new
		scroll.set_policy(Gtk::POLICY_NEVER,Gtk::POLICY_AUTOMATIC)
		scroll.add @users_label
		vbox_left.pack_start(users_label,false)
		vbox_left.pack_start(scroll)
		vbox_right.pack_start(@tabholder)
		vbox_right.pack_start(@commandline,false)
#		vbox_right.pack_start(cocks_button)
		@mainwindow.add(vbox)
		@mainwindow.border_width = 10
		@mainwindow.show_all
		@mainwindow.signal_connect("delete_event") { false }
		@mainwindow.signal_connect("destroy") {
			Gtk.main_quit
			send("QUIT")
			@destroyed = true
		}
		@commandline.signal_connect("activate") do
			text = @commandline.text
			@commandline.text = ""
			parse_command(text)
		end
		@tabholder.signal_connect('switch_page') do |myself,page,page_num|
			if @channeltabs[page_num] == @lastactive then
#				@statusbar.pop(@statusbar.get_context_id("activity")) #FIXME: wtf mate
#				@statusbar.push(@statusbar.get_context_id("activity"),"#{timestamp} Idle")
			end
			@channellabel[@channeltabs[page_num]].text= @channeltabs[page_num] if @channellabel[@channeltabs[page_num]]
			for scroll in @channelscroll
				@channeltabs[@tabholder.page_num(scroll[1])] = scroll[0]
			end
			@commandline.grab_focus
			if @topics[@channeltabs[page_num]] and @topics[@channeltabs[page_num]][0] then
				@title_label.text = @topics[@channeltabs[page_num]][0]
			else
				@title_label.text = "XSirc"
			end
			if @pretty_users[@channeltabs[page_num]] then
				@users_label.buffer.text = @pretty_users[@channeltabs[page_num]].sort.join("\n")
			else
				@users_label.buffer.text = "Not a channel!"
			end
		end
		@accel = Gtk::AccelGroup.new
		@mainwindow.add_accel_group(@accel)
		@accel.connect(Gdk::Keyval::GDK_n,Gdk::Window::CONTROL_MASK,Gtk::ACCEL_VISIBLE) {
			puts "Ctrl-N"
			@tabholder.next_page
		}
		@accel.connect(Gdk::Keyval::GDK_p,Gdk::Window::CONTROL_MASK,Gtk::ACCEL_VISIBLE) {
			puts "Ctrl-P"
			@tabholder.prev_page
		}
		@accel.connect(Gdk::Keyval::GDK_g,Gdk::Window::CONTROL_MASK,Gtk::ACCEL_VISIBLE) {
			puts "Ctrl-G"
			@commandline.grab_focus
		}
		@accel.connect(Gdk::Keyval::GDK_t,Gdk::Window::CONTROL_MASK,Gtk::ACCEL_VISIBLE) {
			if @channelscroll[@channeltabs[@tabholder.page]].vadjustment.value == @channelscroll[@channeltabs[@tabholder.page]].vadjustment.upper then
				puts "Value equals Upper; Value is #{@channelscroll[@channeltabs[@tabholder.page]].vadjustment.value}, upper is #{@channelscroll[@channeltabs[@tabholder.page]].vadjustment.upper}"
			elsif @channelscroll[@channeltabs[@tabholder.page]].vadjustment.value == (@channelscroll[@channeltabs[@tabholder.page]].vadjustment.lower - @channelscroll[@channeltabs[@tabholder.page]].vadjustment.page_size ) then
				puts "Value equals Lower; Value is #{@channelscroll[@channeltabs[@tabholder.page]].vadjustment.value}, lower is #{@channelscroll[@channeltabs[@tabholder.page]].vadjustment.lower}"
			else
				puts "Value in the middle; Value is #{@channelscroll[@channeltabs[@tabholder.page]].vadjustment.value}, upper is #{@channelscroll[@channeltabs[@tabholder.page]].vadjustment.upper}, lower is #{@channelscroll[@channeltabs[@tabholder.page]].vadjustment.lower}"
			end
			puts @channelscroll[@channeltabs[@tabholder.page]].vadjustment.page_size.to_s
		}
		open_view('network')
		@commandline.grab_focus
	end
	
	def parse_command(command)
		command = Iconv.conv('ISO-8859-1','utf-8',command)
		if command.start_with?('//') then
			if command.start_with?('//me ') then
				privmsg(@channeltabs[@tabholder.page],"#{1.chr}ACTION "+command.sub('//me ','').strip+1.chr)
			elsif command.start_with?('//names') then
				if !@channeltabs[@tabholder.page].start_with?('#') then
					add_to_view(@channeltabs[@tabholder.page],"NAMES list: This is not a channel")
				else
					add_to_view(@channeltabs[@tabholder.page],"In this chan: #{@pretty_users[@channeltabs[@tabholder.page].downcase].join(', ')}")
				end
			elsif command.start_with?('//close') then
				if @channeltabs[@tabholder.page] == 'network' then
					add_to_view(@channeltabs[@tabholder.page],"Can't close this.")
				elsif !@channeltabs[@tabholder.page].start_with?('#') then
					@tabholder.remove_page(@tabholder.page)
				else
					add_to_view(@channeltabs[@tabholder.page],"Hey, part the channel first!")
				end
			elsif command == '//topic' then
				@commandline.text = "/topic #{@channeltabs[@tabholder.page]} :#{@title_label.text}"
			elsif command == '//connect' then
				if !@socket then
					add_to_view(@channeltabs[@tabholder.page],"(Re)connecting...")
					connect()
				end
			elsif command == '//disconnect' then
				if @socket then
					add_to_view(@channeltabs[@tabholder.page],"Disconnecting!")
					disconnect()
				end
			elsif command.start_with?('//eval ') then
				eval(command.sub('//eval ',''))
			end
		elsif command.start_with?('/') and !command.start_with?('/ ') then
			command = command.sub('/','').strip
			if @socket then
				if command.downcase.start_with?('/quit') then
					send command
					@connected = false
				else
					send command
				end
			else
				add_to_view('network',"Not connected!")
			end
		else
			if command.start_with?('/ ') then
				privmsg(@channeltabs[@tabholder.page],command.sub('/ ','').strip)
			else
				privmsg(@channeltabs[@tabholder.page],command.strip)
			end
		end
	end
	
	# Fun fact: I don't fully comprehend my old code
	def open_view(channel)
		@channelscroll[channel.downcase] = Gtk::ScrolledWindow.new
		@channeltext[channel.downcase] = Gtk::TextView.new
		@channeltext[channel.downcase].modify_font(Pango::FontDescription.new("monospace 9"))
		@channeltext[channel.downcase].editable = false
		@channeltext[channel.downcase].cursor_visible = false
		@channeltext[channel.downcase].wrap_mode = Gtk::TextTag::WRAP_WORD
		@channelscroll[channel.downcase].add @channeltext[channel.downcase]
		@channelscroll[channel.downcase].set_policy(Gtk::POLICY_AUTOMATIC,Gtk::POLICY_ALWAYS)
		@channellabel[channel.downcase] = Gtk::Label.new("#{channel}")
		@tabholder.append_page(@channelscroll[channel.downcase],@channellabel[channel.downcase])
		@tabholder.show_all
		@channeltabs[@tabholder.page_num(@channelscroll[channel.downcase])] = channel.downcase
		puts @tabholder.n_pages
	end
	
	def add_to_view(name,text)
		text = Iconv.conv('utf-8','ISO-8859-1',text) # oh god I'm a dumbass
		if !@channeltext[name.downcase] then
			open_view(name.downcase)
		end
		scrolled_all_the_way = false
		if @channeltext[name.downcase] then
			if @channelscroll[name.downcase].vadjustment.value.to_i == (@channelscroll[name.downcase].vadjustment.upper - @channelscroll[name.downcase].vadjustment.page_size).to_i then # If scrollbar is all the way down
				scrolled_all_the_way = true
			end
			@channeltext[name.downcase].buffer.insert(@channeltext[name.downcase].buffer.end_iter,"#{timestamp()} #{text.chomp}\n")
			if scrolled_all_the_way
				end_mark = @channeltext[name.downcase].buffer.create_mark(nil,@channeltext[name.downcase].buffer.end_iter,false)
				@channeltext[name.downcase].scroll_to_mark(end_mark,0.0,true,0.0,1.0)
			end
			if @channeltabs[@tabholder.page] != name.downcase then
				@channellabel[name.downcase].set_markup("<span foreground=\"red\">#{name.downcase}</span>")
				@lastactive = name.downcase
			end
		end
	end
	
	def timestamp(time=Time.new)
		return time.strftime(@config['global']['timestamp_format'])
	end
end
